/***************************************************************************
 *   Copyright (C) 2007 by Autostop team                                   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
///@file gnetserver.cpp Base class for TCP server

#include <QtGlobal>

// Qt
#if QT_VERSION >= 0x040300
	#ifndef QT_NO_OPENSSL
		#include <QSslSocket>
		#include <QSslCipher>
		#include <QSslCertificate>
		#include <QSslKey>
	#else
		#include <QTcpSocket>
	#endif
#else
	#include <QTcpSocket>
#endif	

// gcommon
#include "gexception.h"

// local
#include "gnettypes.h"
#include "gnetpackets.h"
#include "gnetserver.h"

// --------------------------- constructor -----------------------------------
GNetServer::GNetServer( QObject* pParent /* =NULL*/ ) 
	: QObject( pParent ), _server( this )
{
	_pSocket = NULL;
	_timeout = 10000; // 10 sec
	
	_authorized = false;

#if QT_VERSION >= 0x040300 && !defined QT_NO_OPENSSL
	_pSocket = new QSslSocket();
#else
	_pSocket = new QTcpSocket();
#endif	
}

// --------------------------- destructor ------------------------------------
GNetServer::~GNetServer()
{
	// nothing
}

// ---------------------- GNetServer::setSocket ------------------------------
void GNetServer::setSocket( QTcpSocket * pSocket )
{
	disconnectFromHost(); // just in case	
	
	qDebug() << "\n-------------------- loading cert";
	QFile file2( "privkey.pem" );
	file2.open( QIODevice::ReadOnly );
	QSslKey key( &file2, QSsl::Rsa );
    qDebug() << "Key is null?" << key.isNull();
	_pSocket->setPrivateKey( key );

	QFile file( "cacert.pem" );
    file.open( QIODevice::ReadOnly );
	QSslCertificate cert( &file );
    qDebug() << "Cert is null?" << cert.isNull();
	_pSocket->setLocalCertificate( cert );
	
	if( pSocket )
	{
		_pSocket->setSocketDescriptor( pSocket->socketDescriptor(),
										pSocket->state(),
										pSocket->openMode()
										);
		qDebug() << "\n-------------------- entering";
		qDebug() << "    state::" << _pSocket->state();
		qDebug() << "     mode::" << _pSocket->mode();
		qDebug() << "encrypted::" << _pSocket->isEncrypted();
		_pSocket->startServerEncryption();
// 		_pSocket->ignoreSslErrors();
	}
	if ( ! _pSocket->waitForEncrypted( 10000 ) )
	{
		qDebug() << "encryption failed" << _pSocket->error();
		QList<QSslError> errors = _pSocket->sslErrors();
		foreach( QSslError err, errors )
		{
			qDebug() << "sslError" << (int)(err.error()) << err;
		}
		throw GTimeoutError( _pSocket->errorString() );
	}
	else
	{
		qDebug() << "encrypted";
	}
	qDebug() << "\n-------------------- leaving";
	qDebug() << "    state::" << _pSocket->state();
	qDebug() << "     mode::" << _pSocket->mode();
	qDebug() << "encrypted::" << _pSocket->isEncrypted();
	
// #else
// 	_pSocket = pSocket;
// #endif	
	
	processAuthorization();
	
}
// ---------------------------------------------------------------------------


// ---------------------- GNetServer::setSocket ------------------------------
#if QT_VERSION >= 0x040300 && !defined QT_NO_OPENSSL
void GNetServer::setSocket( QSslSocket * pSocket )
{
	disconnectFromHost(); // just in case
	delete _pSocket;
	_pSocket = pSocket;
	
	if( _pSocket )
	{
		_pSocket->startServerEncryption();
		processAuthorization();
	}
}
#endif
// ---------------------------------------------------------------------------


// -------------------- GNetServer::setSocketDescriptor() --------------------
void GNetServer::setSocketDescriptor( int socketDescriptor,
									  QAbstractSocket::SocketState socketState,
									  QIODevice::OpenMode openMode
									)
{
	if( _pSocket )
	{
#if QT_VERSION >= 0x040300 && !defined QT_NO_OPENSSL
	_pSocket->startServerEncryption();
// 	if ( ! _pSocket->waitForEncrypted( 10000 ) )
// 	{
// 		qDebug() << "encryption failed" << _pSocket->error();
// 		QList<QSslError> errors = _pSocket->sslErrors();
// 		qDebug() << "sslerror size" << errors.size();
// 		if( errors.size() > 0 )
// 		{
// 			qDebug() << "sslError" << errors[0].error();
// 		}
// 		throw GTimeoutError( _pSocket->errorString() );
// 	}
#endif
		processAuthorization();
	}
}
// ---------------------------------------------------------------------------


// --------------------------- listen ----------------------------------------
/// Listens synchronously for connection on specified port/address.
/// When connection arrives, it is accepted and used for further communication
void GNetServer::listen( quint16 port, const QHostAddress & address /*= QHostAddress::Any*/ )
{
	// disconnect current connection
	disconnectFromHost();

	
	// listen
	if ( ! _server.listen( address, port ) )
	{
		throw GSysError( _server.errorString() );
	}

	// wait for new connection
	if ( ! _server.waitForNewConnection(-1) )
	{
		throw GSysError( _server.errorString() );
	}
	
	// accept connection
	QTcpSocket * pNewConnection = _server.nextPendingConnection();
	if ( ! pNewConnection )
	{
		throw GSysError( tr("No incoming conection" ) );
	}
	
#if QT_VERSION >= 0x040300 && !defined QT_NO_OPENSSL
	if( !_pSocket ) _pSocket = new QSslSocket( this );
#endif
	
	setSocket( pNewConnection );
	
#if QT_VERSION >= 0x040300 && !defined QT_NO_OPENSSL
	_pSocket->startServerEncryption();
#endif
	
	
		// check for valid password 
	processAuthorization();
}


// --------------------------- isConnected -----------------------------------
bool GNetServer::isConnected() const
{
	if ( _pSocket )
	{
		return  _pSocket->state() == QAbstractSocket::ConnectedState;
		// NOTE thiswas com,mented and changed to '_pSocket != NULL'. Why?
	}
	
	return false;
}


// --------------------------- disconnectFromHost ----------------------------
void GNetServer::disconnectFromHost()
{
	if ( _pSocket )
	{
		if( isConnected() )
		{
			_pSocket->disconnectFromHost();
			if ( ! _pSocket->waitForDisconnected( _timeout ) )
			{
				throw GTimeoutError( _pSocket->errorString() );
			}
		}
	}
}


// --------------------------- processPackets --------------------------------
/// Starts packet processing, does not return.
/// Throws configuration exception if network connection is not
/// Established. Throws other exceptions on communication errors
void GNetServer::processPackets()
{
	// check config
	if ( ! isConnected() )
		throw GConfigurationError( tr("No connection established") );

	if( ! _authorized )
		throw GConfigurationError( tr("No authentication.") );
		
	
	// process packets here
	forever
	{
		GNetPacket * pPacket = waitForPacket();
		
		if ( pPacket )
		{
            processPacket( pPacket );
            delete pPacket;
		}
		else
		{
			break;
		}
	}
	
}


// --------------------------- waitForPacket ---------------------------------
/// Waits for incomng packet
GNetPacket* GNetServer::waitForPacket()
{
	if ( isConnected() )
	{
		GHeader header;
		
		// read header
		header.readFromSocket( _pSocket, -1 );
		qDebug() << "HEADER OK ";
		// create apropriate packet
		GNetPacket * pPacket = GNetPacketFactory::instance()->createPacket( header );
		// read packet
		if ( pPacket )
		{
			pPacket->readFromSocket( _pSocket, _timeout );
			
			return pPacket;
		}
		
	}

	return NULL;
}


// --------------------------- sendPacket ------------------------------------
/// Sends packet
void GNetServer::sendPacket( const GNetPacket & packet )
{
	if ( isConnected() )
	{
		GHeader header = packet.header();

		header.writeToSocket( _pSocket, _timeout );
		packet.writeToSocket( _pSocket, _timeout );
		
	}
}



// ============================== processAuthorization =======================
void GNetServer::processAuthorization()
{
	setAuthorized( false );
	
	// create answer packet
	GNetPacketAuthorizationAnswer * pAuthorizationAnswer = new GNetPacketAuthorizationAnswer();
	

	qDebug() << "waiting for authorization packet";
	GNetPacket* pPacket = waitForPacket();
	
	if( pPacket->packetType() == GNetPacket::GNETPACKET_AUTHORIZATION && pAuthorizationAnswer )
	{
		GNetPacketAuthorization * pPacketAuthorization;
		pPacketAuthorization = qobject_cast<GNetPacketAuthorization *>( pPacket );
		
		if( pPacketAuthorization )
		{
			QString login    = pPacketAuthorization->login();
			QString password = pPacketAuthorization->password();
			switch( passUser( login, password ) )
			{
				case GNetPacketAuthorizationAnswer::STATUS_OK :
				{
					setAuthorized( true );
						// send answer
					pAuthorizationAnswer->setStatus( GNetPacketAuthorizationAnswer::STATUS_OK );
					sendPacket( *pAuthorizationAnswer );
				}
				break;
				
				case GNetPacketAuthorizationAnswer::STATUS_LOGIN_INCORRECT :
				{
					// send answer
					pAuthorizationAnswer->setStatus( GNetPacketAuthorizationAnswer::STATUS_LOGIN_INCORRECT );
					pAuthorizationAnswer->setMessage( tr("Invalid user name.") );
					sendPacket( *pAuthorizationAnswer );
					disconnectFromHost();
				}
				break;
				
				case GNetPacketAuthorizationAnswer::STATUS_PASSWORD_INCORRECT :
				{
					// send answer
					pAuthorizationAnswer->setStatus( GNetPacketAuthorizationAnswer::STATUS_PASSWORD_INCORRECT );
					pAuthorizationAnswer->setMessage( tr("Invalid password.") );
					sendPacket( *pAuthorizationAnswer );
					disconnectFromHost();
				}
				break;

				default:
					break;
			}
		}
	}
}


// eof


